﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using log4net;

namespace Et.Open.Socket.Demo.core
{
    public class SocketProtocolWrapper
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(SocketProtocolWrapper));

        private System.Net.Sockets.Socket sock;

        private static readonly byte[] ProtocolHead = new byte[3]
        {
            2,
            2,
            2
        };

        private static readonly byte[] ProtocolTail = new byte[3]
        {
            3,
            3,
            3
        };

        private const double TimeToleranceSeconds = 600.0;

        private static MD5 md5 = new MD5CryptoServiceProvider();

        private string pid;

        public SocketProtocolWrapper(System.Net.Sockets.Socket sock, string pid)
        {
            this.sock = sock;
            this.pid = pid;
        }

        internal ApiData ReceiveRequestData()
        {
            int num = ProtocolHead.Length;
            int num2 = ProtocolTail.Length;
            byte[] array = new byte[num + 2];
            sock.Receive(array, array.Length, SocketFlags.None);
            if (!ProtocolHead.SequenceEqual(array.Take(num)))
            {
                throw new EtSocketDataValidationException("包头错误");
            }

            int num3 = BitConverter.ToUInt16(array, num);
            byte[] array2 = new byte[num3 + num2];
            sock.Receive(array2, array2.Length, SocketFlags.None);
            if (!ProtocolTail.SequenceEqual(array2.Skip(num3).Take(num2)))
            {
                throw new EtSocketDataValidationException("包尾错误");
            }

            ulong unixTimeStamp = BitConverter.ToUInt64(array2, 0);
            DateTime d = UnixTimeStampToDateTime(unixTimeStamp);
            DateTime now = DateTime.Now;
            double num4 = Math.Abs((now - d).TotalSeconds);
            if (num4 > 600.0)
            {
                throw new EtSocketDataValidationException(string.Format("超出有效时间（请求时间戳 - {0}， 服务器时间 - {1}）", d.ToString("yyyy-MM-dd HH:mm:ss"), now.ToString("yyyy-MM-dd HH:mm:ss")));
            }

            byte[] array3 = new byte[num3 - 16 + 5];
            Buffer.BlockCopy(array2, 0, array3, 0, array3.Length - 5);
            Buffer.BlockCopy(Encoding.ASCII.GetBytes(pid), 0, array3, array3.Length - 5, 5);
            byte[] first = md5.ComputeHash(array3);
            if (!first.SequenceEqual(array2.Skip(num3 - 16).Take(16)))
            {
                throw new EtSocketDataValidationException("MD5摘要验证失败");
            }

            return new ApiData
            {
                DataType = BitConverter.ToUInt16(array2, 8),
                Data = Encoding.UTF8.GetString(array2, 10, num3 - 26)
            };
        }

        internal void SendResponseData(ApiData resp)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(resp.Data);
            ushort num = (ushort)(10 + bytes.Length + 16);
            byte[] array = new byte[ProtocolHead.Length + 2 + num + ProtocolTail.Length];
            int num2 = 0;
            Buffer.BlockCopy(ProtocolHead, 0, array, 0, ProtocolHead.Length);
            num2 += ProtocolHead.Length;
            Buffer.BlockCopy(BitConverter.GetBytes(num), 0, array, num2, 2);
            num2 += 2;
            Buffer.BlockCopy(BitConverter.GetBytes(DateTimeToUnixTimestamp(DateTime.Now)), 0, array, num2, 8);
            num2 += 8;
            Buffer.BlockCopy(BitConverter.GetBytes(resp.DataType), 0, array, num2, 2);
            num2 += 2;
            Buffer.BlockCopy(bytes, 0, array, num2, bytes.Length);
            num2 += bytes.Length;
            byte[] array2 = new byte[num - 16 + 5];
            Buffer.BlockCopy(array, ProtocolHead.Length + 2, array2, 0, num - 16);
            Buffer.BlockCopy(Encoding.ASCII.GetBytes(pid), 0, array2, num - 16, 5);
            Buffer.BlockCopy(md5.ComputeHash(array2), 0, array, num2, 16);
            num2 += 16;
            Buffer.BlockCopy(ProtocolTail, 0, array, num2, ProtocolTail.Length);
            sock.Send(array);
        }

        public static DateTime UnixTimeStampToDateTime(ulong unixTimeStamp)
        {
            return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTimeStamp).ToLocalTime();
        }

        public static ulong DateTimeToUnixTimestamp(DateTime dateTime)
        {
            return (ulong)(TimeZoneInfo.ConvertTimeToUtc(dateTime) - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
        }
    }
}
